# -*- coding: utf-8 -*-

"""
@author: Laowang
@contact: QQ:1125564921
@Created on: 2022/6/21
@Remark: 
"""

import configparser

import threading
import smtplib
import logging
import datetime
import importlib
import time

from abc import ABC
#from turtle import pd
from queue import Queue, Empty
from email.header import Header
from logging import Logger, config
from functools import reduce
from ast import literal_eval
from itertools import chain
from email.message import EmailMessage
from typing import Dict, List, Type, Union

from framework_strategy.gopy.main.engine import EventEngine, Event
from framework_strategy.gopy.utils.file import log_path, file_exist
from framework_strategy.gopy.base.constant import EVENT_LOG, Exchange
from framework_strategy.gopy.base import Self, BaseAPI, API
from framework_strategy.gopy.trader.object import LogData
from framework_strategy.gopy.app.template import ExampleStrategyTemplate
from framework_strategy.gopy import settings
from framework_strategy.gopy.settings import *
from framework_strategy.gopy.strategy.engine import StrategyRegister, StrategyLoad, APP_NAME  # NOQA


class MainEngine:

    """
    Acts as the core of strategy Trader.
    TODO Consider whether to switch on or off a single engine and gateway and a single policy 
    """

    def __init__(self, event_engine: EventEngine = None) -> None:

        assert isinstance(event_engine, EventEngine) == True
        self.event_engine: EventEngine = event_engine or EventEngine()
        self.engines: Dict[str, BaseEngine] = dict()
        self._exchanges: List[Exchange] = []

        """
        gateways: For registering action events, 
            you can define gateways for different platforms in the Gateways directory.  
            After the definition is complete, 
            MainEngine is used to add the corresponding gateway, 
            and then the policy can be routed according to the specified gateway  
            In the corresponding request, 
            the gateway method can refer to the API internal method definition 
        """
        self._gateways: Dict[str, BaseAPI] = dict()
        self._api: BaseAPI = None

        self.init_engine()

    def init_engine(self):
        """ Start the event-driven engine """

        self.event_engine.start()
        self.add_engine(EmailEngine)
        self.add_engine(LogEngine)
        self.add_engine(MiddlewareBase)
        self.add_engine(StrategyManager)  # 默认加载启动所有的
        self.init_api()

    def init_api(self):
        self._api = API(self.event_engine)

    @property
    def client(self):
        """ api-request """
        return self._api

    def add_engine(self, engine_cls: "BaseEngine") -> "BaseEngine":
        """
        Add function engine
        """

        engine = engine_cls(self, self.event_engine)
        self.engines[engine.engine_name] = engine
        return engine

    def add_gateway(self, gateway_cls: Type[BaseAPI]) -> BaseAPI:
        """
        Add gateway 
        """

        gateway = gateway_cls(self, self.event_engine)
        self._gateways[gateway.gateway_name] = gateway

        for exchange in gateway.exchanges:
            if exchange not in self._exchanges:
                self._exchanges.append(exchange)
        return gateway

    def get_engine(self, engine_name: str) -> Union["BaseEngine", None]:
        """ return engine """

        engine = self.engines.get(engine_name, None)
        if not engine:
            self.write_log("The specified engine was not found",
                           engine_name)
        return engine

    def get_gateway(self, gateway_name: str) -> Union[Type[BaseAPI], None]:
        """ return gateway """

        gateway = self._gateways.get(gateway_name, None)
        if not gateway:
            self.write_log("The specified gateway was not found",
                           gateway_name)
        return gateway

    def write_log(self, msg: str, source: str = ""):
        """ write log """

        log = LogData(msg=msg, source=source)
        event = Event(EVENT_LOG, log)
        self.event_engine.put(event)

    def get_all_gateway_names(self) -> List[str]:
        """ return all gateways name"""

        return list(self._gateways.keys())

    def get_all_gateways(self) -> List[Type[BaseAPI]]:
        """ return all gateways obj"""

        return list(self._gateways.values())

    def get_all_exchanges(self) -> List[str]:
        """ return all exchanges """

        return self._exchanges

    def get_all_engines(self) -> List["BaseEngine"]:
        """ return all engines """

        return list(self.engines.values())

    def get_all_engine_names(self) -> List[str]:
        """ return all engine names """

        return list(self.engines.keys())

    def close(self):
        """ stop """
        self.event_engine.stop()
        for obj in chain(self.engines.values(), self._gateways.values()):
            obj.close()


class BaseEngine(ABC):
    """
    Abstract class for implementing an function engine.
    """

    def __init__(self,
                 main_engine: MainEngine,
                 event_engine: EventEngine,
                 engine_name: str
                 ) -> None:

        self.main_engine: MainEngine = main_engine
        self.event_engine: EventEngine = event_engine
        self.engine_name: str = engine_name

    def close(self):
        """"""
        pass


class EmailEngine(BaseEngine):

    """ email sending function """
    _interval = 3

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:
        engine_name: str = "email"
        super().__init__(main_engine, event_engine, engine_name)

        self._active: bool = False
        self._queue: Queue = Queue()
        self.__thread = threading.Thread(
            target=self.run, daemon=True, name="EmailEngine")
        self.main_engine.send_email = self.send_email
        self.start()

    def start(self):
        self._active = True
        self.__thread.start()

    def run(self):
        for _ in iter(lambda: self._active, False):
            try:
                msg = self._queue.get(block=True, timeout=self._interval)
                with smtplib.SMTP_SSL(
                    SETTINGS["email.server"], SETTINGS["email.port"]
                ) as smtp:
                    smtp.login(
                        SETTINGS["email.username"], SETTINGS["email.password"]
                    )
                    smtp.send_message(msg)
            except Empty:
                pass

        print("[EmailEngine]  exit... ")

    def send_email(self, subject: str, content: str, receiver: str = ""):
        if not self._active:
            self.start()

        if not receiver:
            receiver = SETTINGS["email.receiver"]

        msg = EmailMessage()
        msg["From"] = Header(SETTINGS["email.sender"], "utf-8")
        msg["To"] = Header(receiver, "utf-8")
        msg["Subject"] = subject
        msg.set_content(content)
        self._queue.put(msg)

    def close(self):
        self._active = False
        self.__thread.join()


class LogEngine(BaseEngine):

    """ log records event """
    # TODO 带加入日志切割
    LOGCONFIG = "log.ini"
    Adt: str = datetime.datetime.now().strftime('%Y%m%d')
    HANDLE = "handler_rootHandler"

    def __new__(cls, *args, **kwargs) -> Self:

        if not hasattr(cls, "_instance"):
            setattr(cls, "_instance", super().__new__(cls))
        return getattr(cls, "_instance")

    def __init__(self,
                 main_engine: MainEngine,
                 event_engine: EventEngine,
                 ) -> None:
        engine_name: str = "log"
        super().__init__(main_engine, event_engine, engine_name)

        log_file = log_path(self.LOGCONFIG)
        if file_exist(log_file):
            cp = configparser.RawConfigParser()
            if hasattr(log_file, 'readline'):
                cp.read_file(log_file)
            else:
                cp.read(log_file)
            origin = literal_eval(cp[self.HANDLE]["args"])
            value = reduce(
                lambda x, y: x + y, ((os.path.join(settings.SETTINGS["log.path"], file),) for file in origin))
            cp[self.HANDLE]["args"] = repr(value)
            logging.config.fileConfig(cp)
            self.logger: Logger = logging.getLogger("root")
        else:
            self.logger: Logger = logging.getLogger("root")
            ch = logging.StreamHandler()
            self.logger.setLevel(logging.INFO)
            # create formatter
            formatter = logging.Formatter(
                '%(asctime)s - %(name)s - %(levelname)s - %(message)s')

            # add formatter to ch
            ch.setFormatter(formatter)
            # add ch to logger
            self.logger.addHandler(ch)

        self.register_event()

    def process_log_event(self, event: Event):

        log = event.event_data
        type = getattr(log, "source", None) or getattr(log, "level", None)
        msg = "[-{}-] : {}".format(type, log.msg)
        logging.log(log.level, msg)

    def register_event(self):
        """ log event """
        self.event_engine.on_bind(EVENT_LOG, self.process_log_event)


class StrategyEngine(BaseEngine):

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:
        engine_name = "stragtegy"
        super().__init__(main_engine, event_engine, engine_name)


class MiddlewareBase(BaseEngine):
    """
    Request middleware, 
    which handles event processing before and after a request
    request_before  param : request
    response_after  param : after
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:

        self.engine_name = "middleware"
        super().__init__(main_engine, event_engine, self.engine_name)
        self.main_engine.__dict__[self.engine_name] = []

        self.__request_before = []
        self.__response_after = []

        self.load_middleware()

    def _load_middleware(self):

        pkgs = settings.REQUEST_RESPONSE_MIDDLEWARE
        for pkg in pkgs:
            module_pkg, _module = ".".join(
                pkg.split(".")[:-1]), pkg.split(".")[-1]
            try:
                module = importlib.import_module(module_pkg)
            except FileNotFoundError:
                continue
            else:
                __module = getattr(module, _module, None)
                if not __module:
                    raise ModuleNotFoundError(_module)
            self.main_engine.__dict__[self.engine_name].append(__module)

            instance = __module()
            if hasattr(__module, "request_before"):
                self.__request_before.append(
                    (getattr(__module, "request_before"), instance))

            if hasattr(__module, "response_after"):
                self.__response_after.append(
                    (getattr(__module, "response_after"), instance))

    def load_middleware(self):
        setattr(self.main_engine, "request_before", self._request_before)
        setattr(self.main_engine, "response_after", self._response_after)
        setattr(self.main_engine, "prepare", self.prepare)
        self._load_middleware()

    def middlewares(self):
        yield from self.main_engine.__dict__[self.engine_name]

    def _request_before(self, request, *args, **kwargs):
        for handle, _instance in self.__request_before:
            handle(_instance, request, *args, **kwargs)

    def _response_after(self, response, *args, **kwargs):
        for handle, _instance in self.__response_after:
            handle(_instance, response, *args, **kwargs)

    def prepare(self, request_handle, *args, **kwargs):

        self._request_before(request_handle, *args, **kwargs)
        response = request_handle()
        self._response_after(response, *args, **kwargs)
        return response


class StrategyTemplate(ExampleStrategyTemplate, BaseEngine):
    """"""

# strategy manager


class ProcessManger(BaseEngine):
    # TODO 考虑运行的方案
    # TODO 前期考虑pm2 后面考虑k8s动态拓展
    def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:

        engine_name = "process"
        super().__init__(main_engine, event_engine, engine_name)
        # setattr(main_engine, engine_name, self)

        # strategy info
        self._modules = {}

    def start(self, module):
        # TODO
        module.start()

    def stop_all(self, modules):
        # TODO
        for module in modules:
            module.close()
        pass

    def stop(self, module):
        # TODO
        pass

    def start_all(self, modules):
        # TODO
        for module in modules:
            module.start()


class StrategyManager(StrategyTemplate):

    #  Start/stop policy management
    #  Information management for policy execution
    def __init__(self, main_engine: MainEngine, event_engine: EventEngine, engine_name: str = APP_NAME) -> None:
        super().__init__(main_engine, event_engine, engine_name)

        self._queue = Queue()
        self.main_engine, self.event_engine = main_engine, event_engine
        self.strategyRegister = StrategyRegister()
        self.strategyRegister.from_cls_init(main_engine, event_engine)

        self.process = ProcessManger(main_engine, event_engine)
        self.strategyLoad = StrategyLoad(
            self.strategyRegister, queue=self._queue)
        self.strategyLoad.start()

        self.thread = threading.Thread(
            target=self.start_all, daemon=True, name="StrategyManager")
        self._sm = dict()
        self._active = False
        self.run()

    def close(self):
        self._active = False
        self.stop_all()
        self.strategyLoad.stop()
        self.thread.join()

    def stop(self, strategy_name):
        """ """
        strategy = self.strategyRegister.get_strategy_class(strategy_name)
        self.process.stop(strategy)

    def stop_all(self):
        """ stop all strategy """
        self.process.stop_all(self._sm.values())

    def start_all(self):
        # TODO 循环加载

        while self._active:
            try:
                values = self._queue.get(timeout=3)
                if not values and len(values) < 0:
                    # self.process.start_all(self.strategyRegister.classes.values())
                    time.sleep(1)
                    continue
                for k, v in values.items():
                    if k in self._sm:
                        time.sleep(1)
                        continue

                    instance = v(self.main_engine, self.event_engine)
                    self._sm[k] = instance
                    self.start(instance)
            except Empty:
                pass

        print("[StrategyManager] exit....")

    def run(self):
        self._active = True
        self.thread.start()

    def start(self, model):
        self.process.start(model)

# class OmsEngine(BaseEngine):
#     订单聚合
#     订单拆分
#     订单批量状态获取
#     订单历史获取 | 待调研
#     """ Registers events for API calls """
#     def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:
#         engine_name = "OMS"
#         super().__init__(main_engine, event_engine, engine_name)

#         self._api : Dict = dict()
